<html>
    <head>
        <title>更新状态中的对象</title>
    </head>
    <body>
        <script>
            // 更新状态中的对象
                    /*
                        状态可以保存任何类型的JavaScript值，包括对象。
                        但是你不应该直接修改你在React状态在的对象。而是，当你想更新一个对象时，你需要创建一个新对象(或复制一个现有对象)，然后"使用"
                    */


                    // 一、什么是突变(mutation)?
                           
                            // 首先，你可以在状态中存储任意类型的JavaScript值(bool、数组、数字、对象等等)

                            const [x,setx] = useState(1);

                            /*
                                到目前为止，你一直在处理 数字、字符串 和布尔值。  这些类型的值是"不可变的"(immutable) ，表示"不可更改" 或 "read-only".
                                你可以通过替换它们的值以触发一次重新渲染。
                            */
                            
                            setx(5);

                            /*
                                状态 x 从 0 变为5 ，但是数字本身并没发生变化。在JavaScript中，无法对内置的原始数值，如数字、字符串、和布尔值进行任何改变。

                            */

                             // 现在考虑 state 中存放对象的情况：

                                const [postion,setPosition] = useState({x:0,y:0});

                            // 从技术上讲，可以改变对象自身的内容(属性值)。  当你这样做的时候就制造了一个"突变"（mutation）

                                postion.x=5;

                            /*
                                然而，虽然严格来说React状态中存放的对象是"可变的"，但是你应该像处理　数字、布尔、字符串一样，
                                将它们视为"不可变的" 。 因此你应该替换它们的值，而不是对它们进行修改。
                            */

                    
                    // 二、将状态(state)视为"只读的"

                            /*
                                换句话说，你应该"把所有存放在状态(state)中的JavaScript对象都视为只读的"。
                            */

                            /*
                                eg： 下面的例子中，我们用一个存在方在状态中的对象来表示鼠标指针所在的位置。当你在预览区域触摸或移动鼠标时

                                     红色的点本应该移动。但是实际上红点仍然停留在原处：
                            */
                             
                                    import { useState } from 'react';
                                    export default function MovingDot() {
                                        const [position, setPosition] = useState({
                                            x: 0,
                                            y: 0
                                        });
                                        return (
                                                <div
                                                        onPointerMove={e => {
                                                            position.x = e.clientX;
                                                            position.y = e.clientY;
                                                        }}
                                                        style={{
                                                            position: 'relative',
                                                            width: '100vw',
                                                            height: '100vh',
                                                        }}>
                                                        <div style={{
                                                            position: 'absolute',
                                                            backgroundColor: 'red',
                                                            borderRadius: '50%',
                                                            transform: `translate(${position.x}px, ${position.y}px)`,
                                                            left: -10,
                                                            top: -10,
                                                            width: 20,
                                                            height: 20,
                                                        }} />
                                                </div>
                                        );
                                    }

                            /*
                                    问题出在下面这段代码上：
                                                 onPointerMove={e => {
                                                            position.x = e.clientX;
                                                            position.y = e.clientY;
                                                        }} 
                                                            
                                    这段代码修改了上次渲染状态快照里分配给 position 的对象。
                                    但是因为没有使用状态的"设置函数"，React并不知道对象已经被更改。所以，React没有作出任何反映。
                                    虽然，在一些情况下，直接修改状态(state)可能是有效的，但我们并不推荐这么做。
                                    你应该吧在渲染过程中可以访问到的状态(state)视为"只读的"，不要视图直接使用 state =....这样的方式去更改状态。


                                   
                            */

                            //  在这种情况下，为了真正地"触发一次重新渲染" 你需要创建一个想对象并把它传递给"状态设置函数"：
                              
                                      /*
                                            onPointerMove={e => {
                                                setPosition({
                                                    x: e.clientX,
                                                    y: e.clientY
                                                });
                                            }}
                                      */

                                    /*
                                        通过使用 setPosition ,你在告诉React：
                                            ①、使用这个新的对象替换 postion 的值
                                            ②、然后再次渲染这个组件
                                    */

                                                import { useState } from 'react';
                                                export default function MovingDot() {
                                                            const [position, setPosition] = useState({
                                                                x: 0,
                                                                y: 0
                                                            });
                                                            return (
                                                                <div
                                                                        onPointerMove={e => {
                                                                            setPosition({
                                                                            x: e.clientX,
                                                                            y: e.clientY
                                                                            });
                                                                        }}
                                                                        style={{
                                                                            position: 'relative',
                                                                            width: '100vw',
                                                                            height: '100vh',
                                                                        }}>
                                                                <div style={{
                                                                                position: 'absolute',
                                                                                backgroundColor: 'red',
                                                                                borderRadius: '50%',
                                                                                transform: `translate(${position.x}px, ${position.y}px)`,
                                                                                left: -10,
                                                                                top: -10,
                                                                                width: 20,
                                                                                height: 20,
                                                                            }} />
                                                                </div>
                                                            );
                                                }
                                                // 现在，你就能看到随着鼠标移动，红点也会移动了。

                    // 三、使用展开语法复制对象
                                /*
                                    在之前的例子中，始终会根据当前鼠标指针的位置创建一个新的 posotion 对象
                                    但是通常，你会希望把"现有的数据"作为你所创建的"新对象"的一部分。
                                    eg： 你可能只想要更新表单中的一个字段，其他的字段仍然使用之前的值。
                                */

                                    // eg: 下面的代码中，输入框并不会正常运行，因为 onChange 事件处理程序直接修改了状态，而不是通过"状态设置函数"
                                            import { useState } from 'react';

                                            export default function Form() {
                                                    const [person, setPerson] = useState({
                                                        firstName: 'Barbara',
                                                        lastName: 'Hepworth',
                                                        email: 'bhepworth@sculpture.com'
                                                    });

                                                    function handleFirstNameChange(e) {
                                                        person.firstName = e.target.value;
                                                    }

                                                    function handleLastNameChange(e) {
                                                        person.lastName = e.target.value;
                                                    }

                                                    function handleEmailChange(e) {
                                                        person.email = e.target.value;
                                                    }

                                                    return (
                                                        <>
                                                            <label>
                                                                First name:
                                                                <input
                                                                    value={person.firstName}
                                                                    onChange={handleFirstNameChange}
                                                                    />
                                                            </label>
                                                            <label>
                                                                Last name:
                                                                <input
                                                                    value={person.lastName}
                                                                    onChange={handleLastNameChange}
                                                                    />
                                                            </label>
                                                            <label>
                                                                Email:
                                                                    <input
                                                                    value={person.email}
                                                                    onChange={handleEmailChange}
                                                                    />
                                                            </label>
                                                            <p>
                                                                {person.firstName}{' '}
                                                                {person.lastName}{' '}
                                                                ({person.email})
                                                            </p>
                                                        </>
                                                    );
                                            }


                                /*
                                    想要实现你的需求，最可靠的办法就是创建一个新的对象，并将他传递给setPerson.
                                    但是在这里，你还需要"把当前的数据赋值到你新创建的对象里"，因为你只改变了其中一个字段：


                                    你可以使用 "..."(对象展开语法)，这样你就不需要单独赋值每一个属性：

                                          setPerson({
                                            ...persion,  // 复制一个 person中的所有属性
                                            firstName: e.target.value  // 重写firstName属性（覆盖 firstName属性）
                                          })


                                    请注意 "..."展开语法本质是"浅拷贝"----它只会复制一层。
                                    这使得它的执行速度很快，但是也意味着当你需要更新一个"嵌套属性"时，你必须得多次使用展开语法。
                                */
                                    
                    // 四、更新一个"嵌套对象"
                              
                            // 考虑下面这种结构的嵌套对象
                                  const [person,setPserson] = useState({
                                        name:'yubing',
                                        artwork:{
                                            title:'Blue Nana',
                                            city:'xuzhou',
                                            image:'https://i.imgur.com/Sd1AgUOm.jpg'
                                        }
                                  });

                            // 如果你想要更新 persion.artwork.city的值，用突变(moutation)来实现的方法非常容易理解：
                                person.artwork.city ='nanjing'

                            /*
                                但是，在React中，你需要将状态(state)视为不可变的！ 为了修改city的值，你首先需要创建一个新的artwork对象，
                                然后创建一个新的 person对象，并使得其中的 artwork属性指向新创建的 artwork对象：
                            */
                                  const nextArtwork ={...person.artwork,city:'New Delhi'}
                                  const nexePerson ={...persion,artwork:nextArtwork}
                                  setPosition(nextPserson);

                                  // 或者，写成一个函数调用：
                                  setPerson({
                                        ...person, // 复制其它字段的数据 
                                        artwork: { // 替换 artwork 字段 
                                            ...person.artwork, // 复制之前 person.artwork 中的数据
                                            city: 'New Delhi' // 但是将 city 的值替换为 New Delhi！
                                        }
                                  });

                            
                    // 五、使用Immerse编写简洁的更新逻辑
                            /*
                                 如果你的 state 有多层的嵌套，你或许应该考虑将其扁平化。
                                 但是，如果你不想改变 state 的数据结构，你可能更喜欢用一种更便捷的方式来实现嵌套展开的效果。
                                 Immer 是一个非常流行的库，它可以让你使用简便但可以直接修改的语法编写代码，并会帮你处理好复制的过程。
                                 通过使用 Immer，你写出的代码看起来就像是你“打破了规则”而直接修改了对象： 
                            */


                            /*
                                  尝试使用 Immer:
                                        运行 npm install use-immer 添加 Immer 依赖
                                        用 import { useImmer } from 'use-immer' "替换掉"  import { useState } from 'react'
                            */
                                 // eg:
                                        updatePerson(draft => {
                                                draft.artwork.city = 'Lagos';
                                        });

                                    // 但是，不同于一般的突变(mutation),它并不覆盖之前的状态(state)

                                        import { useImmer } from 'use-immer';

                                        export default function Form() {
                                            const [person, updatePerson] = useImmer({
                                                name: 'Niki de Saint Phalle',
                                                artwork: {
                                                title: 'Blue Nana',
                                                city: 'Hamburg',
                                                image: 'https://i.imgur.com/Sd1AgUOm.jpg',
                                                }
                                            });

                                            function handleNameChange(e) {
                                                updatePerson(draft => {
                                                    draft.name = e.target.value;
                                                });
                                            }

                                            function handleTitleChange(e) {
                                                updatePerson(draft => {
                                                    draft.artwork.title = e.target.value;
                                                });
                                            }

                                            function handleCityChange(e) {
                                                updatePerson(draft => {
                                                    draft.artwork.city = e.target.value;
                                                });
                                            }

                                            function handleImageChange(e) {
                                                updatePerson(draft => {
                                                    draft.artwork.image = e.target.value;
                                                });
                                            }

                                            return (
                                                    <>
                                                        <label>
                                                            Name:
                                                            <input
                                                                    value={person.name}
                                                                    onChange={handleNameChange}
                                                            />
                                                        </label>
                                                        <label>
                                                            Title:
                                                            <input
                                                                    value={person.artwork.title}
                                                                    onChange={handleTitleChange}
                                                            />
                                                        </label>
                                                        <label>
                                                            City:
                                                            <input
                                                                    value={person.artwork.city}
                                                                    onChange={handleCityChange}
                                                            />
                                                        </label>
                                                        <label>
                                                            Image:
                                                            <input
                                                                    value={person.artwork.image}
                                                                    onChange={handleImageChange}
                                                            />
                                                        </label>
                                                        <p>
                                                            <i>{person.artwork.title}</i>
                                                            {' by '}
                                                            {person.name}
                                                            <br />
                                                            (located in {person.artwork.city})
                                                        </p>
                                                        <img 
                                                            src={person.artwork.image} 
                                                            alt={person.artwork.title}
                                                        />
                                                    </>
                                            );
                                        }

                                /*
                                        可以看到，事件处理函数变得更简洁了。
                                        你可以随意在一个组件中同时使用 useState 和 useImmer。
                                        如果你想要写出更简洁的更新处理函数，Immer 会是一个不错的选择，
                                        尤其是当你的 state 中有嵌套，并且复制对象会带来重复的代码时。
                                */
        </script>
    </body>
</html>